﻿using System;
using System.Collections.ObjectModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using AzukiMap.Layer;
using AzukiMap.Tools.Logger;
using Geotools.Geometries;
using System.Xml.Linq;

namespace AzukiMap.Map
{
    public delegate void MapExtentChangedHandler(object sender, MapExtentChangedArgs e);
    public delegate void MapScaleChangedHandler(object sender, MapScaleChangedArgs e);
    public delegate void MapProjectionChangedHandler(object sender, MapProjectionChangedArgs e);
    public delegate void MapRotationChangedHandler(object sender, MapRotationChangedArgs e);
    public delegate void MouseMovedHandler(object sender, MouseMovedArgs e);
    public delegate void LayerAddedHandler(object sender, LayerAddedArgs e);

    public class MapControl 
    {
        #region events 

        public event MapExtentChangedHandler MapExtentChanged;
        public event MapScaleChangedHandler MapScaleChanged;
        public event MapProjectionChangedHandler MapProjectionChanged;
        public event MapRotationChangedHandler MapRotationChanged;
        public event MouseMovedHandler MouseMoved;
        public event LayerAddedHandler LayerAdded;

        protected virtual void OnMapExtentChanged(MapExtentChangedArgs e)
        {
            if (MapExtentChanged != null)
            {
                MapExtentChanged(this, e);
            }
        }

        protected virtual void OnMapScaleChanged(MapScaleChangedArgs e)
        {
            if (MapScaleChanged != null)
            {
                MapScaleChanged(this, e);
            }
        }

        protected virtual void OnMapProjectionChanged(MapProjectionChangedArgs e)
        {
            if (MapProjectionChanged != null)
            {
                MapProjectionChanged(this, e);
            }
        }

        protected virtual void OnMapRotationChanged(MapRotationChangedArgs e)
        {
            if (MapRotationChanged != null)
            {
                MapRotationChanged(this, e);
            }
        }

        protected virtual void OnMouseMoved(MouseMovedArgs e)
        {
            if (MouseMoved != null)
            {
                MouseMoved(this, e);
            }
        }

        protected virtual void OnLayerAdded(LayerAddedArgs e)
        {
            if (LayerAdded != null)
            {
                LayerAdded(this, e);
            }
        }

        #endregion

        # region fields
        MapCoordinate _mapCoordinate;
        MapMouseControl _mouseControl;
        LayerManager _layerManager;
        Canvas _rootCanvas;
        Rect _clipRect;
        # endregion

        # region properties

        public Canvas RootCanvas
        {
            get { return _rootCanvas; }
        }

        public MapCoordinate MapCoordinate
        {
            get { return _mapCoordinate; }
        }

        public ObservableCollection<ILayer> Layers
        {
            get { return _layerManager.Layers; }
        }

        # endregion

        # region constructor

        public MapControl(Canvas rootCanvas)
        {
            Logger.Debug("MapControl.MapControl: init starts.");

            _clipRect = new Rect();
            _rootCanvas = PrepereRootCanvas(rootCanvas);
            _mouseControl = new MapMouseControl(_rootCanvas);
            Canvas layerContainer = _mouseControl.LayerContainer; 
            _layerManager = new LayerManager(layerContainer);

            _rootCanvas.SizeChanged += new SizeChangedEventHandler(_rootCanvas_SizeChanged);
            _mouseControl.LayerContainerMoved += new LayerContainerMovedHandler(mouseControl_ContainerMoved);
            _mouseControl.ScreenScaleChanged += new ScreenScaleChangedHandler(mouseControl_ScreenScaleChanged);
            _mouseControl.MouseMovedOnScreen += new MouseMovedOnScreenHandler(mouseControl_MouseMoved);

            _mapCoordinate = new MapCoordinate();

            // TODO should remove
            //_layerContainer.Background = new SolidColorBrush(Colors.LightGray);


            Logger.Debug("MapControl.MapControl: init finshed.");
        }

        # endregion

        # region canvas settings

        Canvas PrepereRootCanvas(Canvas _rootCanvas)
        {
            Logger.Debug("MapControl.PrepereRootCanvas");

            // if the background is null mouse events on the canvas are not fired.
            if (_rootCanvas.Background == null)
            {
                _rootCanvas.Background = new SolidColorBrush(Colors.White);
            }
            // set the clip in the _rootCanvas
            if (_rootCanvas.Clip == null)
            {
                //SetClipOnRootCanvas(_rootCanvas.ActualWidth, _rootCanvas.ActualHeight);
            }

            //_rootCanvas.LayoutUpdated += new EventHandler(rootCanvas_LayoutUpdated);
            _rootCanvas.SizeChanged += new SizeChangedEventHandler(_rootCanvas_SizeChanged);
            //_rootCanvas.LayoutUpdated += new EventHandler(SetMapContext);
            _rootCanvas.UpdateLayout();
            return _rootCanvas;
        }



        void SetClipOnRootCanvas(double width, double height)
        {
            Logger.Debug(
                "MapControl.SetClipOnRootCanvas:"
                 + " width = " + width.ToString()
                 + " height= " + height.ToString()
                );

            if (width == 0 && height == 0)
            {
                return;
            }
            _clipRect.X = 0;
            _clipRect.Y = 0;
            _clipRect.Width = width;
            _clipRect.Height = height;
            RectangleGeometry clipGeom = new RectangleGeometry();
            clipGeom.Rect = _clipRect;
            _rootCanvas.SetValue(Canvas.ClipProperty, clipGeom);
        }

        # endregion

        #region MapControl

        public double GetScreenWidth()
        {
            return _rootCanvas.ActualWidth;
        }

        public double GetScreenHeight()
        {
            return _rootCanvas.ActualHeight;
        }

        public GTPolygon GetExtents()
        {
            return _mapCoordinate.CurrentExtent;
        }

        public void MoveTo(double lon, double lat)
        {
            Logger.Debug(
                "MapControl.MoveTo(2):"
                 + " lon = " + lon.ToString()
                 + " lat = " + lat.ToString()
                );

            _mapCoordinate.MoveTo(lon, lat);
            FireMapExtentChanged(_mapCoordinate.CurrentExtent);
        }

        public void MoveTo(double lon, double lat, double newScale)
        {
            Logger.Debug(
                "MapControl.MoveTo(3):"
                 + " lon = " + lon.ToString()
                 + " lat = " + lat.ToString()
                 + " newScale = " + newScale.ToString()
                );

            _mapCoordinate.MoveTo(lon, lat, newScale);
            FireMapExtentChanged(_mapCoordinate.CurrentExtent);
            FireMapScaleChanaged(_mapCoordinate.GeoScale, _mapCoordinate.ScreenScale);
        }

        public void MoveToExtents(double minLon, double minLat, double maxLon, double maxLat)
        {
            Logger.Debug(
                "MapControl.MoveToExtent(3):"
                 + " minLon = " + minLon.ToString()
                 + " minLat = " + minLat.ToString()
                 + " maxLon = " + maxLon.ToString()
                 + " maxLat = " + maxLat.ToString()
                );

            _mapCoordinate.MoveToExtent(minLon, minLat, maxLon, maxLat);
            _mouseControl.Refresh();

            FireMapExtentChanged(_mapCoordinate.CurrentExtent);
            FireMapScaleChanaged(_mapCoordinate.GeoScale, _mapCoordinate.ScreenScale);
        }

        public void SetScale(double newScale)
        {
            Logger.Debug(
                "MapControl.SetScale():"
                 + " newScale = " + newScale.ToString()
                );

            _mapCoordinate.ScreenScale = newScale;

            GTPolygon extent = _mapCoordinate.CurrentExtent;
            FireMapExtentChanged(extent);
            FireMapScaleChanaged(_mapCoordinate.GeoScale, _mapCoordinate.ScreenScale);
        }

        public GTPoint GetPixPointFromGeo(double lon, double lat)
        {
            return _mapCoordinate.GetPixPointFromGeo(lon, lat);
        }

        public MapCanvas CreateMapCanvas()
        {
            MapCanvas mapCanvas = new MapCanvas(_mapCoordinate);
            return mapCanvas;
        }

        public void AddLayer(ILayer mapLayer)
        {
            _layerManager.Add(mapLayer);
            FireLayerAdded(mapLayer);
        }

        public void AddLayer(ILayer mapLayer, int position)
        {
            _layerManager.Add(mapLayer, position);
            FireLayerAdded(mapLayer);
        }

        public void AddLayerTop(ILayer mapLayer)
        {
            _layerManager.AddTop(mapLayer);
            FireLayerAdded(mapLayer);
        }

        public void AddLayerButtom(ILayer mapLayer)
        {
            _layerManager.AddButtom(mapLayer);
            FireLayerAdded(mapLayer);
        }

        void FireLayerAdded(ILayer mapLayer)
        {
            LayerAddedArgs e = new LayerAddedArgs(_layerManager.Layers, mapLayer);
            OnLayerAdded(e);
        }

        public XElement ToXml()
        {
            return _layerManager.ToXml();
        }

        void FireMapExtentChanged(GTPolygon extent)
        {
            MapExtentChangedArgs e = new MapExtentChangedArgs(extent);
            OnMapExtentChanged(e);
            _layerManager.DrawAll();
        }

        void FireMapScaleChanaged(double geoScale, double screenScale)
        {
            MapScaleChangedArgs e = new MapScaleChangedArgs(geoScale, screenScale);
            OnMapScaleChanged(e);
            _layerManager.RemoveAllDrawings();
            _layerManager.DrawAll();
        }

        void FireMouseMoved(GTPoint geoPoint, Point mousePoint)
        {
            MouseMovedArgs e = new MouseMovedArgs(geoPoint, mousePoint);
            OnMouseMoved(e);
        }

        #endregion

        #region handler

        //void rootCanvas_LayoutUpdated(object sender, EventArgs e)
        void _rootCanvas_SizeChanged(object sender, SizeChangedEventArgs e)
        {
            if (_rootCanvas == null)
            {
                return;
            }
            double w = _rootCanvas.ActualWidth;
            double h = _rootCanvas.ActualHeight;
            /*
            if (_layerContainer != null)
            {
                _layerContainer.Width = w;
                _layerContainer.Height = h;
            }
             */
            if (_mapCoordinate != null)
            {
                _mapCoordinate.SetScreenSize(w, h);
                FireMapScaleChanaged(_mapCoordinate.GeoScale, _mapCoordinate.ScreenScale);
            }
            SetClipOnRootCanvas(w, h);
        }

        void mouseControl_ScreenScaleChanged(object sender, ScreenScaleChangedArgs e)
        {
            _mouseControl.IsLocked = true;
            _mapCoordinate.AdjX = e.AdjX;
            _mapCoordinate.AdjY = e.AdjY;
            double scale = _mapCoordinate.ZoomByRatioAndAnchor(1 / e.ScaleRatio, e.MoveX, e.MoveY);
            FireMapScaleChanaged(_mapCoordinate.GeoScale, _mapCoordinate.ScreenScale);
            FireMapExtentChanged(_mapCoordinate.CurrentExtent);
            _mouseControl.IsLocked = false;
        }

        void mouseControl_ContainerMoved(object sender, LayerContainerMovedArgs e)
        {
            _mouseControl.IsLocked = true;
            _mapCoordinate.AdjX = e.AdjX;
            _mapCoordinate.AdjY = e.AdjY;
            _mapCoordinate.MoveByPixel(e.MoveX, e.MoveY);
            FireMapExtentChanged(_mapCoordinate.CurrentExtent);
            _mouseControl.IsLocked = false;
        }

        void mouseControl_MouseMoved(object sender, MouseMovedOnScreenArgs e)
        {
            GTPoint geoPoint = _mapCoordinate.GetGeoPointFromPixel(e.MouseX, e.MouseY);
            Point mousePoint = new Point(e.MouseX, e.MouseY);
            FireMouseMoved(geoPoint, mousePoint);
        }

        #endregion

        #region IMapStatus Members

        public GTPolygon CurrentExtent
        {
            get { return _mapCoordinate.CurrentExtent; }
        }

        public GTPolygon PreviousExtent
        {
            get { return _mapCoordinate.LastExtent; }
        }

        public int Srid
        {
            get { return _mapCoordinate.Srid; }
            set { _mapCoordinate.Srid = value; }
        }

        public double Tolerance
        {
            get { throw new NotImplementedException(); }
        }

        public bool FlipY
        {
            get { return _mapCoordinate.FlipY; }
        }

        public double ScreenScale
        {
            get { return _mapCoordinate.ScreenScale; }
        }

        public double GeoScale
        {
            get { return _mapCoordinate.GeoScale; }
        }

        public double Angle
        {
            get { return _mapCoordinate.Angle; }
        }

        #endregion
    }
}
